

package com.robot.myapplication;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class FlowLayout extends ViewGroup {
    private static final String TAG="FlowLayout:";
    private int mHorizontalSpacing=dp2px(16);//每个item横向间距
    private int mVerticalSpacing=dp2px(16);//每个item竖向间距
    private List<List<View>>allLines=new ArrayList<>();//记录所有的行，一行行的存储,用于layoyt;
    List<Integer>lineHeights=new ArrayList<>();//记录每一行的行高,用于layout;



    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout  (Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    private void clearMeasureParams() {
        allLines.clear();
        lineHeights.clear();
    }

    //度量
    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        clearMeasureParams();//内存抖动
        //先度量孩子
        int childCount = getChildCount();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);//ViewGroup解析的父亲给我的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);//ViewGroup解析的父亲给我的高度

        List<View> lineViews = new ArrayList<>();//保存一行中所有的view
        int lineWidthUsed=0;//记录这行已经使用了多宽的size
        int lineHeight=0;//一行的行高

        int parentNeedeHeight=0;//Measure过程中，子view要求的父viewGroup的高
        int parentNeedeWidth=0;//Measure过程中，子view要求的父viewGroup的宽
        //计算自己本身子view需要的宽高
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams childP = childView.getLayoutParams();
            if (childView.getVisibility() != View.GONE) {

            //将layoutParams转变为measureSpec
            //todo :MeasureSpec是View中的内部类，基本都是二进制运算。由于int是32位的，用高两位表示mode，低30位表示size，MODE_SHIFT = 30的作用是移位
            // TODO：MeasureSpec大厂面试经常会问到的，熟悉掌握：LayoutParams 是什么？与MeasureSpec有关系吗

            /**
             * 算法原理:1.传入三个参数:父类的size,父类的边距,自己的大小
             *          2.根据specMode，即case MeasureSpec.EXACTLY，case MeasureSpec.AT_MOST，case MeasureSpec.UNSPECIFIED,
             *          这三种模型进而确定给予自定义的size
             *          UNSPECIFIED：不对View大小做限制，系统使用，对应WRAP_CONTENT
             *          EXACTLY：确切的大小，如：100dp
             *          AT_MOST：大小不可超过某数值，如：MATCH_PARENT, 最大不能超过你爸爸
             *
             */
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childP.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childP.height);
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            //获取子view的度量宽度
            int childMeasuredWidth = childView.getMeasuredWidth();
            //获取子view的度量高度
            int childMeasuredHeight = childView.getMeasuredHeight();

            //如果需要换行
            if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
                //一旦换行，我们就可以判断当前行需要的宽和高了，所以此时要记录下来
                allLines.add(lineViews);
                lineHeights.add(lineHeight);

                parentNeedeHeight = parentNeedeHeight + lineHeight + mVerticalSpacing;
                parentNeedeWidth = Math.max(parentNeedeWidth, lineWidthUsed + mHorizontalSpacing);

                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;
            }

            //view是分行layout的，所以要记录每一行有哪些view,这样可以方便layout布局
            lineViews.add(childView);
            //每行都有自己的宽和高
            lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
            //获取每一行所用到的最大的高度
            lineHeight = Math.max(lineHeight, childMeasuredHeight);

            //处理最后一行数据
            if (i == childCount - 1) {
                allLines.add(lineViews);
                lineHeights.add(lineHeight);

                parentNeedeHeight = parentNeedeHeight + lineHeight + mVerticalSpacing;
                parentNeedeWidth = Math.max(parentNeedeWidth, lineWidthUsed + mHorizontalSpacing);
            }
        }
        }


        //根据子view的度量结果，来重新度量自己的ViewGroup
        /**
         * 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽度来度量
         * 所以它需要自己的Mode类型,是MeasureSpec.EXACTLY将父亲本身的size给它,MeasureSpec.AT_MOST，MeasureSpec.UNSPECIFIED,传入它需要的父亲的大小
         */

        int widthmode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int realWidth=(widthmode==MeasureSpec.EXACTLY)?selfWidth:parentNeedeWidth;
        int realHeight=(heightMode==MeasureSpec.EXACTLY)?selfHeight:parentNeedeHeight;
        //再度量自己,保存
        setMeasuredDimension(realWidth,realHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = allLines.size();

        int curL = getPaddingLeft();
        int curT = getPaddingTop();

        //for循环将每一行view进行布局
        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = allLines.get(i);

            int lineHeight = lineHeights.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);

                int left = curL;
                int top = curT;

                /**
                 * getWidth和getMeasuredWidth区别在于他们的生命周期：
                 *
                 *          getMeasuredWidth在measure()过程结束后就可以获取到对应的值;通过setMeasuredDimension()方法来进行设置的.
                 *          getWidth在layout()过程结束后才能获取到;通过视图右边的坐标减去左边的坐标计算出来的.
                 *
                 */
                //使用此方法在自定义布局时候第一次得到大小0
//                int right = left + view.getWidth();
//                int bottom = top + getHeight();

                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();

                view.layout(curL,curT,right,bottom);
                curL=right+mHorizontalSpacing;
            }
            curT = curT + lineHeight + mVerticalSpacing;
            curL = getPaddingLeft();

        }
    }

    private int dp2px(int dp) {
        //TypedValue.applyDimension()的用法把Android系统中的非标准度量尺寸转变为标准度量尺寸 (Android系统中的标准尺寸是px, 即像素)
        /**
         * 标准单位: px (px是安卓系统内部使用的单位, dp是与设备无关的尺寸单位 )
         *
         * 非标准单位: dp, in, mm, pt, sp
         *
         * TypedValue.applyDimension()方法的功能就是把非标准尺寸转换成标准尺寸, 如:
         *
         * dp->px:  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
         *
         * in->px: TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 20, context.getResources().getDisplayMetrics());
         *
         * mm->px: TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 20, context.getResources().getDisplayMetrics());
         *
         * pt->px: TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 20, context.getResources().getDisplayMetrics());
         *
         * sp->px: TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
         *
         */
        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp, Resources.getSystem().getDisplayMetrics());
    }
}
